package com.sandy.demo.auth

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.springframework.security.authentication.AuthenticationDetailsSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.web.filter.GenericFilterBean

import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

@Slf4j
@CompileStatic
class RestAuthenticationFilter extends GenericFilterBean {

    String endpointUrl = "/restlogin"

    AuthenticationManager authenticationManager

    AuthenticationSuccessHandler authenticationSuccessHandler

    AuthenticationFailureHandler authenticationFailureHandler

    AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource()

    TokenStorageService tokenStorageService = new MemoryTokenStorageService()

    @Override
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        def req = request as HttpServletRequest
        def res = response as HttpServletResponse

        def actualUri = req.requestURI - req.contextPath

        logger.debug "Actual URI is ${actualUri}; endpoint URL is ${endpointUrl}"

        //Only apply filter to the configured URL
        if (actualUri == endpointUrl) {
            log.debug "Applying authentication filter to this request"

            //Only POST is supported
//            if (httpServletRequest.method != 'POST') {
//                log.debug "${httpServletRequest.method} HTTP method is not supported. Setting status to ${HttpServletResponse.SC_METHOD_NOT_ALLOWED}"
//                httpServletResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
//                return
//            }

            def authRequest = extractCredentials(req)

            if (authRequest?.principal && authRequest?.credentials) {
                authRequest.details = authenticationDetailsSource.buildDetails(req)

                try {
                    log.debug "Trying to authenticate the request"
                    def authResult = authenticationManager.authenticate(authRequest)

                    if (authResult.authenticated) {
                        log.debug "Request authenticated. Storing the authentication result in the security context"
                        log.debug "Authentication result: ${authResult}"

                        def accessToken = generateAccessToken(authResult.principal as UserDetails)
                        log.debug "Generated token: ${accessToken}"

                        tokenStorageService.storeToken(accessToken.accessToken, authResult.principal as UserDetails)
                        authenticationSuccessHandler.onAuthenticationSuccess(req, res, accessToken)
                        SecurityContextHolder.context.authentication = accessToken
                    } else {
                        log.debug "Not authenticated. Rest authentication token not generated."
                    }
                } catch (AuthenticationException ae) {
                    log.debug "Authentication failed: ${ae.message}"
                    authenticationFailureHandler.onAuthenticationFailure(req, res, ae)
                }
            } else {
                log.debug "Username and/or password parameters are missing."
                if (!SecurityContextHolder.context.authentication) {
                    res.status = HttpServletResponse.SC_BAD_REQUEST
                    log.debug "Setting status to ${res.status}"
                    return
                } else {
                    log.debug "Using authentication already in security context."
                }
            }
        } else {
            chain.doFilter(request, response)
        }
    }

    UsernamePasswordAuthenticationToken extractCredentials(HttpServletRequest request) {
        def username = request.getParameter("username")
        def password = request.getParameter("password")

        log.debug "Extracted credentials from JSON payload. Username: ${username}, password: ${password?.size() ? '[PROTECTED]' : '[MISSING]'}"

        new UsernamePasswordAuthenticationToken(username, password)
    }

    AccessToken generateAccessToken(UserDetails principal) {
        def token = UUID.randomUUID().toString().replaceAll('-', '')
        return new AccessToken(principal, principal.authorities, token)
    }
}
